package game.code;

import interfaces.IClient;
import interfaces.IServer;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;

import game.util.FReader;
import game.util.FWriter;

/**
 * The game class uses singleton pattern to ensure there is only one 
 * instance of Game. (prevent GUI instances multiple games )
 * 
 * Updated in Stage 3. 
 * Added implements IServer 
 * 
 * June-08-2011
 *  
 * @author Ying
 *
 */
public class Game implements ActionListener {

	/**
	 * singleton pattern
	 */
	private static Game game ;
	
	private final int GAME_TIME = 120000; //in milliseconds
	
	private int highScore; 
	
	private Player player; 
	private Loader loader; 
	
	private Collection<Character> letters ;
	private Clock clock ; 
	
	/**
	 * Instantiate a new Game. uses FReader to load high-score variable
	 * from high-score file. Instantiate a player, which has-a game.
	 * Instantiate a Loader, which loads, select and organizes words(Loader
	 * acts as a back-end of Game). instantiate a game Clock, with game time.
	 * 
	 */
	private Game(){
		highScore = new FReader("high-score").readHighScore();
		player = new Player(this);
		letters = new ArrayList<Character>();
		loader = new Loader("3esl.txt");
		//highScoreWriter = new FWriter("high-score");
		clock = new Clock(GAME_TIME, this );
	}
	
	/**
	 * check if a Game instance is instantiated or not.
	 * @return A singleton Game
	 */
	public static Game getGame(){
		if(game==null){
			game = new Game();
			return game;
		}
		else 
			return game;
	}
	
	
	/**  
	 * @return the game's player. This is useful when GUI wants to know 
	 * the player's information, like current score. 
	 */
	public Player getPlayer(){
		return player;
	}
	
	/**
	 * when game starts, call the Loader to load all words in and select 
	 * a six letter words, then load letters(result lists), which are derived
	 * from the six letters. 
	 * kick-off game clock run.
	 */
	public void start(){
		loader.loadDictionary();
		loader.selectKeyWord();
		loader.loadLetterWords();
		clock.start();
	}
	
	/**
	 * used when the GUI user click start-game button, when the game is running.
	 * It will not reads from the dictionary file again, just select another
	 * six letters, and re-load the result lists. 
	 * reset the player's current score, restart the clock.
	 */
	public void reStart(){
		highScore = new FReader("high-score").readHighScore();
		loader.selectKeyWord();
		loader.loadLetterWords();
		player.score=0;
		clock.restart();
	}
	
	
	/**
	 * @return the high score
	 */
	public int highScore(){
		return highScore;
	}
	
	/**
	 * @return current player's score 
	 */
	public int score(){
		return player.score();
	}
 
	
	/**
	 * if the string submitted is one of the words in result list,
	 * the player's score must go up by 5 points for 3 letter word, 10 
	 * points for 4 letter word, 15 points for a 5 letter word and 20
	 * points for a 6 letter word. 
	 * 
	 * @param word
	 * @return -1 if the String is not word. a non-negative number indicates 
	 * the position within an alphabetical and by-length list of the words. 
	 */
	public int submit(String word ){

		int awardPoints = 0 ;
		int wordPosition = -1 ;

		if(loader.getThreeLetterWords().containsKey(word) 
				&& loader.getThreeLetterWords().get(word).booleanValue()){
			awardPoints = 5; 
			wordPosition = loader.getThreeLetterWords().subMap(
					loader.getThreeLetterWords().firstKey(), word ).size() ;
			//update the loader's result list (set false)
			loader.getThreeLetterWords().put(word, false);
		}
		
		else if(loader.getFourLetterWords().containsKey(word)
				&& loader.getFourLetterWords().get(word).booleanValue()){
			awardPoints = 10 ; 
			wordPosition = loader.getFourLetterWords().subMap(
					loader.getFourLetterWords().firstKey(), word ).size() ;
			loader.getFourLetterWords().put(word, false);
		}
		
		else if(loader.getFiveLetterWords().containsKey(word)
				&& loader.getFiveLetterWords().get(word).booleanValue()){
			awardPoints = 15;
			wordPosition = loader.getFiveLetterWords().subMap(
					loader.getFiveLetterWords().firstKey(), word).size() ;
			loader.getFiveLetterWords().put(word, false);
		}
		else if(loader.getSixLetterWords().containsKey(word)
				&& loader.getSixLetterWords().get(word).booleanValue()){
			awardPoints = 20 ; 
			wordPosition = loader.getSixLetterWords().subMap( 
					loader.getSixLetterWords().firstKey(), word).size() ;
			loader.getSixLetterWords().put(word, false);
		}
		else {
			awardPoints = 0;
		}
		
		player.awardScore(awardPoints);
		
		return wordPosition ; 
	}
	
	/**
	 * 
	 * @return game time left in seconds 
	 */
	public int timeLeft(){
		
		return   clock.timeRemaining( )/1000;
	}
	
	/**
	 * the game is over once the player finds all words, or time runs out
	 * 
	 */
	public void over(){
		//check if the player's score is higher than highscores
		if(player.score()>highScore){
			new FWriter("high-score").writeScoreToFile(player.score());
			
		}
		clock.stop();
		//System.exit(0);
	}
	
	/**
	 * stage 2 method, support GUI display the result lists.
	 * @return an array of ints which indicates how many words there are
	 * of each size. in the returned array, cell n holds the number k of 
	 * words of length n in the solution set. 
	 */
	public int[] numberOfWordsByLength(){
		int [] arr = new int[Loader.KEYWORD_LEN+1];
		
		arr[Loader.MIN_WORD_LEN] = loader.getThreeLetterWords().size();
		arr[Loader.MIN_WORD_LEN+1] = loader.getFourLetterWords().size();
		arr[Loader.MIN_WORD_LEN+2] = loader.getFiveLetterWords().size();
		arr[Loader.MIN_WORD_LEN+3] = loader.getSixLetterWords().size();
		
		return arr ; 
	}
	
	/**
	 * 
	 * @return a Collection of Characters, the six letters available for 
	 * gameplay.
	 */
	public Collection<Character> letters(){
		 
		for(int i = 0; i<Loader.KEYWORD_LEN; i++){
			letters.add(loader.getKeyWord().charAt(i));
		}
		
		return letters;
		
	}
	
	/**
	 * Randomize the six-letter key
	 * @return a scrambled six letters 
	 */
	public Collection<Character> twist( ){
		letters.clear();
		
		char[] arr =  loader.getKeyWord().toCharArray();
		
		for(int i=0; i<Loader.KEYWORD_LEN;i++){
			int j = (int)(Math.random()*Loader.KEYWORD_LEN);
			char temp = arr[j];
			arr[j] = arr[Loader.KEYWORD_LEN-1];
			arr[Loader.KEYWORD_LEN-1] = temp;                                
		}

		for(int i=0 ; i<Loader.KEYWORD_LEN; i++){
			letters.add( arr[i]);
		}
		
		return letters;
	}
	
	/**
	 * return true if the game is run first time.
	 */
	public boolean isFirstRun(){
		return (loader.getKeyWord()==null);
	} 
	/**
	 * @return the Loader's total number of result words
	 */
	public int getTotalNumOfResults(){
		return loader.getTotalNumOfResults();
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		if(e.getSource()==clock){		
			clock.stop();
			System.out.println("Game: Time up, game end.");
			over();
		}
		
	}

}
